<!--数据看板中部球体-->
<template>
    <div class='container'>
        <!--球体盒子-->
        <div id="sphereDiv"></div>
        <!--标签盒子-->
        <div class="tag-div" v-if="currentPointData.pointName"
            :style="{ top: tagPosition.y, left: tagPosition.x, borderColor: dataVColor[0] }">
            <div class="pointName-div">{{ currentPointData.pointName }}</div>
            <div class="pointMsg-div">
                <span :style="{ color: dataVColor[0] }">巡检数量：</span>{{ currentPointData.value }}
            </div>
        </div>
    </div>
</template>
<script>
import * as THREE from "three";
import earthMap from "@/assets/img/earth/map.png";
import mapInverted from "@/assets/img/earth/map_inverted.png";
import dataPointImg from "@/assets/img/earth/dataPoint.png";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
let scene = null, //场景(频繁变更的对象放置在vue的data中会导致卡顿)
    camera = null, //相机
    dom = null, //需要使用canvas的dom
    renderer = null, //渲染器
    anId = "",//动画id
    orbitControls = null, //鼠标控件
    globeRadius = 100, //地球尺寸
    earthGroup = new THREE.Group(), //地球的组
    mouse = new THREE.Vector2(), //鼠标的二维平面
    raycaster = new THREE.Raycaster(); //光线投射器(用于鼠标点击时获取坐标)
export default {
    props: {
        dataVColor: {
            type: Array,
            default: []
        }
    },
    data() {
        return {
            earthMap: earthMap,//球体贴图
            mapInverted: mapInverted,//黑白地图
            dataPointImg: dataPointImg,//数据点
            isTag: true,//标签显示
            currentPointData: {},//当前点数据
            tagPosition: { x: "", y: "" }, //标签位置
            sphereData: []//球体数据
        };
    },
    watch: {},
    methods: {
        //销毁场景
        destroyCom() {
            this.clearGroup(earthGroup);//清除组
            cancelAnimationFrame(anId);//根据动画id停止动画渲染
            renderer.forceContextLoss();//强制失去上下文
            renderer.dispose();
            scene.clear();
            scene = null;//置空
            camera = null;
            renderer = null;
            orbitControls = null;
            document.getElementById("sphereDiv").innerHTML = null;
        },

        //销毁组数据
        clearGroup(group) {
            //清除缓存
            const clearCache = (item) => {
                item.geometry.dispose();//必须对组中的material与geometry进行dispose，清除占用的缓存
                item.material.dispose();
            };
            //移除模型
            const removeObj = (obj) => {
                let arr = obj.children.filter((x) => x);
                arr.forEach((item) => {
                    if (item.children.length) {
                        removeObj(item);
                    } else {
                        clearCache(item);
                        item.clear();
                    }
                });
                obj.clear();
                arr = null;
            };
            removeObj(group);
        },

        //初始化球体
        init(sData) {
            this.sphereData = sData;
            dom = document.getElementById("sphereDiv"); //获取dom
            let width = dom.clientWidth;//dom尺寸
            let height = dom.clientHeight;
            scene = new THREE.Scene(); //场景场景
            camera = new THREE.PerspectiveCamera(45, width / height, 1, 1000); //创建透视相机(视场、长宽比、近面、远面)
            camera.position.set(0, 0, 270); //设置相机位置
            camera.lookAt(0, 0, 0);//相机朝向
            renderer = new THREE.WebGLRenderer({
                antialias: true, //抗锯齿
                alpha: true, //透明
            });//渲染器
            renderer.setSize(width, height); //设置渲染区域尺寸
            dom.appendChild(renderer.domElement); //将渲染器添加到dom中形成canvas
            this.createSphere(); //创建球体
            this.createOrbitControls();//鼠标控制器
            this.render();//渲染器
        },

        //窗口尺寸改变重绘球体
        repaintSphere() {
            let dom = document.getElementById("sphereDiv"); //获取dom
            camera.aspect = dom.clientWidth / dom.clientHeight;//修改相机宽高比
            camera.updateProjectionMatrix();// 更新投影的变换矩阵
            renderer.setSize(dom.clientWidth, dom.clientHeight);//设置渲染器尺寸
        },

        //创建球体
        async createSphere() {
            let globeBufferGeometry = new THREE.SphereGeometry(globeRadius, 50, 50);//球体几何体
            let globeInnerMaterial = new THREE.MeshBasicMaterial({
                map: new THREE.TextureLoader().load(earthMap),//球体材质
                color: this.dataVColor[0],//颜色
                blending: THREE.AdditiveBlending,//纹理融合的叠加方式
                side: THREE.FrontSide,//前面显示
                transparent: false,//透明
                depthWrite: false,//深度写入
                depthTest: false,//黑洞效果
                opacity: .7,//不透明度
            });
            let globeInnerMesh = new THREE.Mesh(
                globeBufferGeometry,
                globeInnerMaterial
            );
            earthGroup.add(globeInnerMesh); //将网格放入地球组
            this.createSpot();//创建球面斑点
            this.createDataPoint(this.sphereData, globeRadius);//创建球面数据点
            await scene.add(earthGroup);//将球体添加到场景中
        },

        //创建球面斑点
        createSpot() {
            let img = new Image();
            img.src = mapInverted; //黑白地图
            //图片加载后绘制斑点至球面
            img.onload = () => {
                let canvas = document.createElement("canvas");
                canvas.width = img.width; //使得canvas尺寸与图片尺寸相同
                canvas.height = img.height;
                canvas.getContext("2d").drawImage(img, 0, 0, img.width, img.height);//canvas绘制图片
                let canData = canvas.getContext("2d").getImageData(0, 0, canvas.width, canvas.height);//获取画布像素数据
                let globeCloudBufferGeometry = new THREE.BufferGeometry(); //设置缓冲几何体
                let globeCloudVerticesArray = []; //地球云缓冲几何体顶点
                let o = null; //数组处理时的计数
                for (o = 0; o < canData.data.length; o += 4) {
                    let r = (o / 4) % canvas.width,
                        i = (o / 4 - r) / canvas.width;
                    if ((o / 4) % 2 == 1 && i % 2 == 1)
                        if (0 === canData.data[o]) {
                            let n = r,
                                longitude = (i / (canvas.height / 180) - 90) / -1, //经度
                                latitude = n / (canvas.width / 360) - 180; //维度
                            let s = this.latLongToVector3(longitude, latitude, globeRadius, .1); //经纬度变换
                            globeCloudVerticesArray.push(s); //将变换后的顶点放入数组
                        }
                }
                let l = new Float32Array(3 * globeCloudVerticesArray.length); //创建顶点数组长度
                for (o = 0; o < globeCloudVerticesArray.length; o++) {
                    l[3 * o] = globeCloudVerticesArray[o].x;//设置顶点数组数据
                    l[3 * o + 1] = globeCloudVerticesArray[o].y;
                    l[3 * o + 2] = globeCloudVerticesArray[o].z;
                }
                let positionVal = new THREE.BufferAttribute(l, 3); //设置缓冲区属性值
                globeCloudBufferGeometry.setAttribute("position", positionVal); //给缓冲几何体添加位置属性
                let globeCloudMaterial = new THREE.PointsMaterial({
                    color: this.dataVColor[0],//颜色
                    depthWrite: false,
                    depthTest: false,
                    fog: true,
                    size: 1.2,
                });//球面斑点材质
                let d = new Float32Array(3 * globeCloudVerticesArray.length), c = [];
                for (o = 0; o < globeCloudVerticesArray.length; o++) {
                    c[o] = this.dataVColor[1];//球面斑点颜色
                    d[3 * o] = c[o].r;//设置地球云数组rgb颜色
                    d[3 * o + 1] = c[o].g;
                    d[3 * o + 2] = c[o].b;
                }
                let color_val = new THREE.BufferAttribute(d, 3);
                globeCloudBufferGeometry.setAttribute("color", color_val);//给缓冲几何体添加颜色属性,修改颜色直接修改globeCloudBufferGeometry的setAttribute
                let globeCloud = new THREE.Points(//球面的象素点
                    globeCloudBufferGeometry,
                    globeCloudMaterial
                );
                globeCloud.name = "globeCloud";
                earthGroup.add(globeCloud); //将地球云添加到地球对象中
            };
        },

        //经纬度转换(经度、纬度、球体半径、距球面距离)
        latLongToVector3(e, a, t, o) {
            let r = (e * Math.PI) / 180,
                i = ((a - 180) * Math.PI) / 180,
                n = -(t + o) * Math.cos(r) * Math.cos(i),
                s = (t + o) * Math.sin(r),
                l = (t + o) * Math.cos(r) * Math.sin(i);
            return new THREE.Vector3(n, s, l); //计算三维向量
        },

        //创建球面数据点
        createDataPoint(data, earthSize) {
            let pointSize = 6;//点尺寸
            let list = JSON.parse(JSON.stringify(data));
            list.forEach((e) => {
                e.color = new THREE.Color(0xffffff);
                if (e.position) {
                    let pointMaterial = new THREE.SpriteMaterial({
                        color: e.color,
                        map: new THREE.TextureLoader().load(dataPointImg),
                        side: THREE.FrontSide, //只显示前面
                    }); //数据点材质
                    let Sprite = new THREE.Sprite(pointMaterial); //点精灵材质
                    Sprite.scale.set(pointSize, pointSize, 1); //点大小
                    let lat = e.position[1]; //纬度
                    let lon = e.position[0]; //经度
                    let s = this.latLongToVector3(lat, lon, earthSize, 1); //坐标转换
                    Sprite.position.set(s.x, s.y, s.z); //设置点的位置
                    Sprite.dotData = e; //将点的数据添加到dotData属性中
                    Sprite.name = "数据点";
                    earthGroup.add(Sprite); //添加进球体组中
                }
            });
        },

        //创建鼠标控制器
        createOrbitControls() {
            orbitControls = new OrbitControls(camera, renderer.domElement);
            orbitControls.enablePan = false; //右键平移拖拽
            orbitControls.enableZoom = true; //鼠标缩放
            orbitControls.enableDamping = true; //滑动阻尼
            orbitControls.dampingFactor = 0.05; //(默认.25)
            orbitControls.minDistance = 180; //相机距离目标最小距离
            orbitControls.maxDistance = 350; //相机距离目标最大距离
            orbitControls.autoRotate = true; //自转(相机)
            orbitControls.autoRotateSpeed = 1; //自转速度
            orbitControls.enableRotate = true;//鼠标左键控制旋转
            orbitControls.minPolarAngle = 0;//倾角范围
            orbitControls.maxPolarAngle = Math.PI;
        },

        //渲染
        render() {
            anId = requestAnimationFrame(this.render);//记录动画id
            document.getElementById("sphereDiv") &&
                document
                    .getElementById("sphereDiv")
                    .addEventListener("mousemove", this.onMousemove, false);
            orbitControls.update(); //鼠标控件实时更新
            renderer.render(scene, camera);
        },

        //鼠标移动事件(光线投射器不要放在vue的data中，会卡顿)
        onMousemove(e) {
            let dom = document.getElementById("sphereDiv");
            let width = dom.clientWidth; //窗口宽度
            let height = dom.clientHeight; //窗口高度
            mouse.x = (e.offsetX / width) * 2 - 1;//将鼠标点击位置的屏幕坐标转换成threejs中的标准坐标
            mouse.y = -(e.offsetY / height) * 2 + 1;
            raycaster.setFromCamera(mouse, camera); //通过鼠标点的位置和当前相机的矩阵计算出raycaster
            let intersects = raycaster.intersectObjects(scene.children); // 获取raycaster直线与网格列表相交的集合
            if (intersects.length !== 0 && intersects[0].object.name == "数据点") {
                (this.isTag) && (this.currentPointData = intersects[0].object.dotData); //intersects列表是按照距离屏幕距离排序的，第一个距屏幕最近
                dom.style.cursor = "pointer"; //光标样式
                this.tagPosition = {
                    x: e.layerX + 20 + "px",
                    y: e.layerY + "px",
                }; //获取标签位置
            } else {
                this.currentPointData = {}; //置空标签数据
                dom.style.cursor = "move"; //光标样式
            }
        },
    },
};
</script>
<style scoped lang='scss'>
.container {
    height: 100%;
    width: 100%;
    overflow: hidden;

    #sphereDiv {
        background-color: rgb(0, 0, 0, .3);
        border-radius: 20px;
        height: 99%;
        width: 100%;
        cursor: move;
    }

    .tag-div {
        padding: 10px 20px;
        background-color: rgba(0, 0, 0, .3);
        color: #fff;
        position: absolute;
        width: auto;
        border: 2px solid transparent;
        text-align: left;
        border-radius: 10px;

        .pointName-div {
            font-size: 20px;
            font-weight: 900;
            color: #fff;
        }

        .pointMsg-div {
            font-size: 15px;
            color: #fff;
            margin: 5px 0px 0px 0px;

            span {
                font-weight: 900;
            }

        }
    }
}
</style>